
package com.py.gs.tools;

import java.io.UnsupportedEncodingException;
import java.nio.charset.MalformedInputException;
import java.util.Collection;
import java.util.List;

import com.alibaba.fastjson.JSONException;
import com.py.gs.util.base.concurrent.BigJsonData;
import com.py.gs.util.net.util.Msg;

/**
 * 高性能的大数据对象转json工具
 * @author KXZ
 */
public final class JBuilder {

	public byte buf[];
	
	/**
	 * The number of chars in the buffer.
	 */
	public int count;

	public JBuilder() {
		this(2048);
	}

	public JBuilder(int capacity) {
		buf = new byte[capacity];
	}
	
	public JBuilder(byte buf[]) {
		this.buf = buf;
	}
	
	public void setLength(int len) {
		count = len;
	}

	public int getBufferLength() {
		return this.buf.length;
	}

	/**
	 * Writes a character to the buffer.
	 */
	public JBuilder append(char c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		buf[count] = (byte) c;
		count = newcount;
		return this;
	}
	
	/**
	 * Writes a boolean to the buffer.
	 */
	public JBuilder append(boolean b) {
		if (b) {
			int newcount = count + 4;
			if (newcount > buf.length) {
				expandCapacity(newcount);
			}
			buf[count++] = 't';
			buf[count++] = 'r';
			buf[count++] = 'u';
			buf[count++] = 'e';
			count = newcount;
		} else {
			int newcount = count + 5;
			if (newcount > buf.length) {
				expandCapacity(newcount);
			}
            buf[count++] = 'f';
            buf[count++] = 'a';
            buf[count++] = 'l';
            buf[count++] = 's';
            buf[count++] = 'e';
		}
		return this;
	}
	
	/**
	 * Writes a character to the buffer.
	 */
	public void write(char c) {
		int newcount = count + 1;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		buf[count] = (byte) c;
		count = newcount;
	}

	/**
	 * Writes characters to the buffer.
	 * 
	 * @param c
	 *            the data to be written
	 * @param off
	 *            the start offset in the data
	 * @param len
	 *            the number of chars that are written
	 */
	public JBuilder append(char c[], int off, int len) {
		if (off < 0 //
				|| off > c.length //
				|| len < 0 //
				|| off + len > c.length //
				|| off + len < 0) {
			throw new IndexOutOfBoundsException();
		} else if (len == 0) {
			return this;
		}

		int newcount = count + len;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		System.arraycopy(c, off, buf, count, len);
		count = newcount;
		return this;

	}

	public byte[] expandCapacity(int minimumCapacity) {
		int newCapacity = buf.length << 1;
		if (newCapacity < minimumCapacity) {
			newCapacity = minimumCapacity;
		}
		byte newValue[] = new byte[newCapacity];
		System.arraycopy(buf, 0, newValue, 0, count);
		buf = newValue;
		return buf;
	}

	/**
	 * Write a portion of a string to the buffer.
	 * 
	 * @param str
	 *            String to be written from
	 * @param off
	 *            Offset from which to start reading characters
	 * @param len
	 *            Number of characters to be written
	 */
	public void write(String str, int off, int len) {
		int newcount = count + len;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		str.getBytes(off, off + len, buf, count);
		count = newcount;
	}

	public int encodeUTF8(char[] chars, int offset, int len, byte[] bytes) {
		int sl = offset + len;
		int dp = 0;
		int dlASCII = dp + Math.min(len, bytes.length);
		
		// ASCII only optimized loop
		while (dp < dlASCII && chars[offset] < '\u0080') {
			bytes[dp++] = (byte) chars[offset++];
		}
		
		while (offset < sl) {
			char c = chars[offset++];
			if (c < 0x80) {
				// Have at most seven bits
				bytes[dp++] = (byte) c;
			} else if (c < 0x800) {
				// 2 bytes, 11 bits
				bytes[dp++] = (byte) (0xc0 | (c >> 6));
				bytes[dp++] = (byte) (0x80 | (c & 0x3f));
			} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { // Character.isSurrogate(c) but 1.7
				final int uc;
				int ip = offset - 1;
				if (Character.isHighSurrogate(c)) {
					if (sl - ip < 2) {
						uc = -1;
					} else {
						char d = chars[ip + 1];
						if (Character.isLowSurrogate(d)) {
							uc = Character.toCodePoint(c, d);
						} else {
							throw new JSONException("encodeUTF8 error", new MalformedInputException(1));
						}
					}
				} else {
					if (Character.isLowSurrogate(c)) {
						throw new JSONException("encodeUTF8 error", new MalformedInputException(1));
					} else {
						uc = c;
					}
				}
				
				if (uc < 0) {
					bytes[dp++] = '?';
				} else {
					bytes[dp++] = (byte) (0xf0 | ((uc >> 18)));
					bytes[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));
					bytes[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));
					bytes[dp++] = (byte) (0x80 | (uc & 0x3f));
					offset++; // 2 chars
				}
			} else {
				// 3 bytes, 16 bits
				bytes[dp++] = (byte) (0xe0 | ((c >> 12)));
				bytes[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));
				bytes[dp++] = (byte) (0x80 | (c & 0x3f));
			}
		}
		return dp;
	}
	
	public int length() {
		return count;
	}

	public String toString() {
		try {
			return new String(buf, 0, count, "utf-8");
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	public void write(String text) {
		if (text == null) {
			writeNull();
		}
		write(text, 0, text.length());
	}
	
	public JBuilder append(int i) {
		if (i == Integer.MIN_VALUE) {
			return append("-2147483648");
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}

		IOUtils.getBytes(i, newcount, buf);

		count = newcount;
		return this;
	}

	 


	public JBuilder append(float value) {
		if (Float.isNaN(value) //
				|| Float.isInfinite(value)) {
			writeNull();
			return this;
		} else {
			String floatText = Float.toString(value);
			if (floatText.endsWith(".0")) {
				floatText = floatText.substring(0, floatText.length() - 2);
			}
			return append(floatText);
		}
	}

	public JBuilder append(double doubleValue) {
		if (Double.isNaN(doubleValue) //
				|| Double.isInfinite(doubleValue)) {
			writeNull();
			return this;
		} else {
			String doubleText = Double.toString(doubleValue);
			if (doubleText.endsWith(".0")) {
				doubleText = doubleText.substring(0, doubleText.length() - 2);
			}
			return append(doubleText);
		}
	}

	public JBuilder append(Enum<?> value) {
		if (value == null) {
			writeNull();
			return this;
		}

		String strVal = value.name();

		if (strVal != null) {
			char quote = '"';
			write(quote);
			append(strVal);
			write(quote);
			return this;
		} else {
			return append(value.ordinal());
		}
	}

	public JBuilder append(long i) {
		if (i == Long.MIN_VALUE) {
			return append("-9223372036854775808");
		}

		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount = count + size;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		IOUtils.getBytes(i, newcount, buf);
		count = newcount;
		return this;
	}

	public void writeNull() {
		write("null", 0, 4);
	}
	

	public JBuilder append(String text) {
		if (text == null) {
			writeNull();
			return this;
		}
		
		int len = text.length();
		int count = this.count;
		int newcount = count + len*4 + 2;
		byte bytes[] = this.buf;
		if (newcount > bytes.length) {
			bytes = expandCapacity(newcount);
		}
		
		bytes[count++] = '\"';
		for (int i = 0; i < len; i++) {
			char c = text.charAt(i);
			if (c < 0x80) {
				switch (c) {
				case '"':
				case '\\': // case '/':
					bytes[count] = '\\';
					bytes[count + 1] = (byte) c;
					count += 2;
					break;
				default:
					bytes[count++] = (byte) c;
					break;
				}
			} else if (c < 0x800) {
				// 2 bytes, 11 bits
				bytes[count++] = (byte) (0xc0 | (c >> 6));
				bytes[count++] = (byte) (0x80 | (c & 0x3f));
			} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { // Character.isSurrogate(c) but 1.7
				final int uc;
				if (Character.isHighSurrogate(c)) {
					if (i == len - 1) {
						bytes[count++] = '?';
						continue;
					} else {
						char d = text.charAt(++i);
						if (Character.isLowSurrogate(d)) {
							uc = Character.toCodePoint(c, d);
						} else {
							throw new JSONException("encodeUTF8 error", new MalformedInputException(1));
						}
					}
				} else {
					if (Character.isLowSurrogate(c)) {
						throw new JSONException("encodeUTF8 error", new MalformedInputException(1));
					} else {
						uc = c;
					}
				}
				bytes[count++] = (byte) (0xf0 | ((uc >> 18)));
				bytes[count++] = (byte) (0x80 | ((uc >> 12) & 0x3f));
				bytes[count++] = (byte) (0x80 | ((uc >> 6) & 0x3f));
				bytes[count++] = (byte) (0x80 | (uc & 0x3f));
			} else {
				// 3 bytes, 16 bits
				bytes[count++] = (byte) (0xe0 | ((c >> 12)));
				bytes[count++] = (byte) (0x80 | ((c >> 6) & 0x3f));
				bytes[count++] = (byte) (0x80 | (c & 0x3f));
			}
		}
		bytes[count++] = '\"';
		this.count = count;
		return this;
	}

 

	public JBuilder appendFieldName(String text) {
		int len = text.length();
		int newcount = count + len + 3;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		int start = count + 1;

		buf[count] = '\"';
		text.getBytes(0, len, buf, start);

		count = newcount;
		buf[count - 2] = '\"';
		buf[count - 1] = ':';
		return this;
	}
	
	public JBuilder appendFieldName(int key) {
		int size = (key < 0) ? IOUtils.stringSize(-key) + 1 : IOUtils.stringSize(key);
		int newcount = count + size;
		if (newcount+1 > buf.length) {
			expandCapacity(newcount+1);
		}
		IOUtils.getBytes(key, newcount, buf);
		count = newcount+1;
		buf[newcount] = ':';
		return this;
	}
	
	public JBuilder appendFieldName(long i) {
		int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);

		int newcount = count + size;
		if (newcount+1 > buf.length) {
			expandCapacity(newcount+1);
		}
		IOUtils.getBytes(i, newcount, buf);
		
		count = newcount+1;
		buf[newcount] = ':';
		return this;
	}
	
	
	public JBuilder appendCommaFieldName(int key) {
		int size = (key < 0) ? IOUtils.stringSize(-key) + 1 : IOUtils.stringSize(key);
		int newcount = count + size +1;
		if (newcount+1 > buf.length) {
			expandCapacity(newcount+1);
		}
		buf[count] = ',';
		IOUtils.getBytes(key, newcount, buf);
		count = newcount+1;
		buf[newcount] = ':';
		return this;
	}
	
	
	public JBuilder appendCommaFieldName(String text) {
		int len = text.length();
		int newcount = count + len + 4;
		if (newcount > buf.length) {
			expandCapacity(newcount);
		}
		buf[count] = ',';
		int start = count + 2;
		buf[count+1] = '\"';
		text.getBytes(0, len, buf, start);
		
		count = newcount;
		buf[count - 2] = '\"';
		buf[count - 1] = ':';
		return this;
	}
	

	/** 设置最后一个字符为c */
	public void reset(char c) {
		buf[count-1] = (byte) c;
	}
	
	public JBuilder appendField(String text, String val) {
		if (val == null) {
			return this;
		}
		appendFieldName(text).append(val);
		return this;
	}

	public JBuilder appendField(String text, int val) {
		appendFieldName(text).append(val);
		return this;
	}

	public JBuilder appendField(String text, long val) {
		appendFieldName(text).append(val);
		return this;
	}

	public void append(int[] vals) {
		write('[');
		for (int i = 0; i < vals.length; i++) {
			if(i != 0) {
				write(',');
			}
			append(vals[i]);
		}
		write(']');
	}
	
	public void append(long[] vals) {
		write('[');
		for (int i = 0; i < vals.length; i++) {
			if(i != 0) {
				write(',');
			}
			append(vals[i]);
		}
		write(']');
	}
	
	public void append(List<Long> longs) {
		write('[');
		for (int i = 0; i < longs.size(); i++) {
			if(i != 0) {
				write(',');
			}
			append(longs.get(i));
		}
		write(']');
	}
	
	
	public JBuilder appendCommaField(String text, String val) {
		if (val == null) {
			return this;
		}
		appendCommaFieldName(text).append(val);
		return this;
	}

	public JBuilder appendCommaField(String text, boolean val) {
		appendCommaFieldName(text).append(val);
		return this;
	}

	public JBuilder appendCommaField(String text, int val) {
		appendCommaFieldName(text).append(val);
		return this;
	}

	public JBuilder appendCommaField(String text, Integer val) {
		if (val == null) {
			return this;
		}
		appendCommaFieldName(text).append(val);
		return this;
	}
	
	public JBuilder appendCommaField(String text, long val) {
		appendCommaFieldName(text).append(val);
		return this;
	}

	public JBuilder appendCommaField(String text, Long val) {
		if (val == null) {
			return this;
		}
		appendCommaFieldName(text).append(val);
		return this;
	}
	
	public JBuilder appendCommaField(String text, BigJsonData data) {
		if (data == null) {
			return this;
		}
		appendCommaFieldName(text);
		data.toJson(this);
		return this;
	}
	
	public JBuilder appendCommaField(String text, Collection<? extends BigJsonData> data) {
		if (data == null) {
			return this;
		}
		appendCommaFieldName(text);
		write('[');
		if (data.size() > 0) {
			for (BigJsonData bd : data) {
				bd.toJson(this);
				append(',');
			}
			buf[count-1] = ']';
		} else {
			write(']');;
		}
		return this;
	}

	public JBuilder appendCommaField(String text, List<Long> longList) {
		if (longList == null) {
			return this;
		}
		appendCommaFieldName(text).append(longList);
		return this;
	}

	public JBuilder appendCommaField(String text, int[] array) {
		if (array == null) {
			return this;
		}
		appendCommaFieldName(text).append(array);
		return this;
	}

	public JBuilder appendCommaField(String text, long[] array) {
		if (array == null) {
			return this;
		}
		appendCommaFieldName(text).append(array);
		return this;
	}

	public void addJson(int type, byte[] bs) {
		int count = this.count;
		if(count+6+bs.length > buf.length){
			expandCapacity(count+6+bs.length);
		}
		byte[] writeCache = buf;
		count+=Msg.writeInt(writeCache, count, bs.length+2);
		count+=Msg.writeShort(writeCache, count, type);
		System.arraycopy(bs, 0, writeCache, count, bs.length);
		count+=bs.length;
		this.count = count;
	}
	
	int mark = 0;
	public void begin(int type){
		int count = this.count;
		if(count + 6 > buf.length){
			expandCapacity(count + 6);
		}
		mark = count;
		Msg.writeShort(buf, count+4, type);
		count += 6;
		this.count = count;
	}
	
	public void end(){
		Msg.writeInt(buf, mark, count-mark-4);
	}

}
